package com.supermap.server;
import com.supermap.SM2Utils;
import com.supermap.Utils;
import com.supermap.crypto.CryproType;
import com.supermap.crypto.CryptoUtils;
import com.supermap.exception.ServerException;
import com.supermap.ipfs.IpfsClient;
import com.supermap.ipfs.IpfsConnection;
import com.supermap.protocol.RpcRequest;
import com.supermap.protocol.RpcResponse;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.PublicKey;
import java.text.SimpleDateFormat;
import java.util.Date;

/*
 * @author liqs
 * @version 1.0
 * @date 2020/12/28 17:58
 * 处理服务端接收的数据
 */
public class ServerHandler extends ChannelInboundHandlerAdapter{

    private static SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");

    /**
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        RpcRequest request = (RpcRequest) msg;
        RpcResponse response = new RpcResponse();

        /**
         * 根据 infoPurposes 字段判断客户端发送信息的用途
         * 此处是为了获取服务端的公钥
         * 状态码标识 2
         */
        if (request.getInfoPurposes().equals("getServerPublickey")) {
            PublicKey clientPublicKey = null;
            if (NettyServer.getCryptoTypeName() == CryproType.getRSA()) {
                clientPublicKey = CryptoUtils.getRSAPublicKeyFromString(request.getPublicKey());
            } else if(NettyServer.getCryptoTypeName() == CryproType.getSM2()) {
                clientPublicKey = SM2Utils.getPublicKeyFromString(request.getPublicKey());
            }
            NettyServer.loadClientPublicKey(clientPublicKey);
            response.setPublicKey(CryptoUtils.RSAPubToString(NettyServer.getServerPublicKey()));
            response.setIpfsIp(request.getIpfsIp());
            response.setIpfsPort(request.getIpfsPort());
            response.setStatus(2);
            ctx.writeAndFlush(response);
            System.out.println(getTime() + " com.suermap.ServerHandler\n" + "INFO: The server sends the public key to the client");
            return;
        }

        /**
         * 根据 infoPurposes 字段判断客户端发送信息的用途
         * close 字段，则表示客户端已经发送完请求
         * 状态码标识 2 标识此时服务端也已经接收完数据，客户端可直接关闭连接
         */
        if (request.getInfoPurposes().equals("closeClient")) {
            response.setStatus(-2);
            ctx.writeAndFlush(response);
            return;
        }

        /**
         * load ClientPub
         */
        PublicKey clientPublicKey = null;
        if (NettyServer.getCryptoTypeName() == CryproType.getRSA()) {
            clientPublicKey = CryptoUtils.getRSAPublicKeyFromString(request.getPublicKey());
        } else if(NettyServer.getCryptoTypeName() == CryproType.getSM2()) {
            clientPublicKey = SM2Utils.getPublicKeyFromString(request.getPublicKey());
        }
        NettyServer.loadClientPublicKey(clientPublicKey);

        /**
         * get hashId
         */
        String hashId;
        if (clientPublicKey == null) {
            hashId = new String(request.getHashId(), "utf-8");
        } else {
            hashId = NettyServer.decrypt(request.getHashId());
            if (ServerException.getException() != null) {
                ServerException.setException("Server decryption failed, please confirm whether the server publickey key loaded by the client is correct");
                saveErrorInfo(ServerException.getException());
                response.setInfoPurposes("server.message");
                response.setStatus(-3);
                response.setError(ServerException.getException());
                ServerException.setException(null);
                response.setIpfsIp(request.getIpfsIp());
                response.setIpfsPort(request.getIpfsPort());
                ctx.writeAndFlush(response);
                return;
            }
        }

        /**
         * get ipfsClient
         */
        String ipfsIp = request.getIpfsIp();
        String ipfsPort = request.getIpfsPort();


        /**
         * 根据客户端发送的信息构建完备节点 ipfsClient
         * 如果构造成功就直接返回结果，上传动作异步处理   状态码 1
         * 如果构造不成功直接返回异常                   状态码 -1
         */
        if (ServerException.getException() != null) {
            saveErrorInfo(ServerException.getException());
            response.setInfoPurposes("server.message");
            response.setStatus(-1);
            response.setError(ServerException.getException());
            ServerException.setException(null);
            response.setHashId(NettyServer.encrypt(hashId));
            ctx.writeAndFlush(response);
            return;
        } else {
            response.setStatus(1);
            ctx.writeAndFlush(response);
        }

        /**
         * 异步向 ipfs 完备节点发送信息（直接返回结果节约时间，然后 server 端进行备份处理）
         * 上传数据成功，不返回任何信息
         * 上传数据失败，返回失败的原因（有可能客户端接收不到，可能已经关闭通道）并打印日志
         */
        ctx.channel().eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                try {
                    if (ServerException.getException() != null ){
                        saveErrorInfo(ServerException.getException());
                        request.setHashId(NettyServer.encrypt(hashId));
                        response.setError(ServerException.getException());
                        ServerException.setException(null);
                        response.setStatus(-1);
                        ctx.writeAndFlush(response);
                        return;
                    } else {

                        /**
                         * 将 hashid 和 ipfs 信息存入到一张表中 防止宕机时仍然可用
                         */
                        // Utils.saveHashIdAndIpfsInfo(hashId, ipfsIp, ipfsPort);
                        Utils.delHashIdAndIpfsInfo(hashId, ipfsIp, ipfsPort);
                    }
                } catch (Exception e) {
                    String msg = getTime() + " com.suermap.ServerHandler \n" +
                            "ERROR: upload file to IPFS [" + ipfsIp + ":" + ipfsPort + " ] failed, file hash id is : " + hashId;
                    saveErrorInfo(msg);
                    System.out.println(msg);
                    request.setHashId(NettyServer.encrypt(hashId));
                    response.setError(ServerException.getException());
                    ServerException.setException(null);
                    response.setStatus(-1);
                    ctx.writeAndFlush(response);
                    return;
                }
            }
        });
    }

    /**
     * 通知处理器最后的channelRead()是当前批处理中的最后一条消息时调用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }


    /**
     * 读操作时捕获到异常时调用
     * @param ctx
     * @param cause
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        ctx.close();
        String msg = getTime() + " com.suermap.ServerHandler.exceptionCaught \n" +
                "ERROR: " + cause.getMessage();
        saveErrorInfo(msg);
        System.out.println(msg);
    }

    /**
     * 客户端去和服务端连接成功时触发
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush("hello client");
    }

    /**
     * 获取系统时间转为常规时间
     * @return
     */
    public static Date getTime() {
        try {
            String systemTime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date(System.currentTimeMillis()));
            return formatter.parse(systemTime);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static int fileLength = 1024 * 1024;

    /**
     * 日志保留，暂时只保留错误日志
     * 日志文件名：日志创建时间
     * 日志单个大小：1024 * 1024 byte
     * log.log 日志为当前最新日志，当大小达到 1024 kb 时就会自动另存一时间开头的日志文件，然后自删除重新记录日志
     * @param msg
     */
    public static void saveErrorInfo(String msg) {
        String filename = "log" + File.separator + "log.log";
        File file = Paths.get(filename).toFile();
        try {
            if (!file.exists()) {
                file = new File(filename);
            }
            FileWriter fileWriter = new FileWriter(filename, true);
            fileWriter.write(msg);
            fileWriter.flush();
            fileWriter.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                // 判断文件大小，然后复制文件夹
                if (file.length() > fileLength) {
                    String loggerFileName =  "log" + File.separator + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date(System.currentTimeMillis())) + ".log";
                    File loggerfile = Paths.get(loggerFileName).toFile();
                    if (!loggerfile.exists()) {
                        loggerfile = new File(loggerFileName);
                    }
                    FileInputStream inputStream = new FileInputStream(filename);
                    FileOutputStream fileOutputStream = new FileOutputStream(loggerFileName);
                    byte[] bytes = new byte[fileLength];
                    int len = -1;
                    while((len = inputStream.read(bytes)) != -1) {
                        fileOutputStream.write(bytes, 0, len);
                    }
                    inputStream.close();
                    file.delete();
                    fileOutputStream.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


    }

}
